﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using JustinAngel.UnitedNationsNews.Framework;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;

namespace JustinAngel.UnitedNationsNews.CustomControls
{
    public class Speech : DependencyObject, INotifyPropertyChanged, IDisposable
    {
        private WebRequestHelper.RequestInfo _currentRequest;
        private bool _ignoreNextBufferNeeded;
        private byte[] _lastPhraseBytes;
        private DynamicSoundEffectInstance _player;
        private bool _resultValid;
        private static ManualResetEvent _testingManualResetEvent;
        private DispatcherTimer _xnaUpdateTimer;

        public static readonly DependencyProperty IsSpeakingProperty = DependencyProperty.Register("IsSpeaking", typeof(bool), typeof(Speech), new PropertyMetadata(false, OnIsSpeakingChanged));

        private static void OnIsSpeakingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var value = (bool)e.NewValue;
            var sender = (Speech)d;

            sender.InvokeIsSpeakingChanged(value);
        }

        public event EventHandler<EventArgs<bool>> IsSpeakingChanged;

        public void InvokeIsSpeakingChanged(bool e)
        {
            EventHandler<EventArgs<bool>> handler = IsSpeakingChanged;
            if (handler != null) handler(this, new EventArgs<bool>(e));
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public event EventHandler ReadyToSpeak;

        public void InvokeReadyToSpeak()
        {
            EventHandler handler = ReadyToSpeak;
            if (handler != null)
                Deployment.Current.Dispatcher.BeginInvoke(() => handler(this, new EventArgs()));
        }


        private static byte[] ConvertTo16BitAudio(byte[] eightBitData, int offset, int length)
        {
            return ConvertTo16BitAudio(ConvertTo16BitSignedArray(eightBitData, offset, length), offset, length);
        }

        private static byte[] ConvertTo16BitAudio(short[] sixteenBitData, int offset, int length)
        {
            List<byte> list = new List<byte>();
            for (int i = offset; i < length; i++)
            {
                list.AddRange(BitConverter.GetBytes(sixteenBitData[i]));
            }
            return list.ToArray();
        }

        private static short ConvertTo16BitSigned(byte b)
        {
            float num = b - 127f;
            if (num == 0f)
                return 0;
            if (num == 128f)
                return 0x7fff;
            if (num == -127f)
                return -32768;
            if (num > 0f)
            {
                num /= 128f;
                num = 32767f * num;
            }
            else
            {
                num /= -128f;
                num = -32768f * num;
            }
            return (short)num;
        }

        private static short[] ConvertTo16BitSignedArray(byte[] bytes, int offset, int length)
        {
            short[] numArray = new short[bytes.Length];
            for (int i = offset; i < length; i++)
            {
                numArray[i] = ConvertTo16BitSigned(bytes[i]);
            }
            return numArray;
        }

        public virtual void Dispose()
        {
            if (_player != null)
            {
                IsSpeaking = false;
                _player.BufferNeeded -= OnPlayerBufferNeeded;
                _player.Stop();
                _player.Dispose();
                _player = null;
                _xnaUpdateTimer.Stop();
                _xnaUpdateTimer = null;
                FrameworkDispatcher.Update();
                _ignoreNextBufferNeeded = true;
            }
        }

        public void StartTextToSpeech(string toString, string fromString = "", string fromLang = "en", string toLang = "en")
        {
            if (_currentRequest != null)
            {
                _currentRequest.Cancelled = true;
            }
            _currentRequest = InitiateRequest(GetUriRequest(toString, fromLang, toLang), fromString, fromLang, toString, toLang);
        }

        private string GetUriRequest(string text, string fromLang, string toLang)
        {
            return string.Format("http://api.microsofttranslator.com/v2/Http.svc/Speak?appId={0}&from={2}&language={3}&text={1}", new object[] { "6711AE32090B54D69071A6E2C29C9369624FE468", Uri.EscapeDataString(text), fromLang, toLang });
        }

        private WebRequestHelper.RequestInfo InitiateRequest(string uriRequest, string fromString, string fromLang, string toString, string toLang)
        {
            LastPhraseBytes = null;
            return WebRequestHelper.SendByteRequest(uriRequest, () => ResultValid = false,
             resultBytes =>
             {
                 _lastPhraseBytes = resultBytes;
                 ResultValid = true;
                 InvokeReadyToSpeak();
                 TestingManualResetEvent.Set();
             },
            errorMsg => /*TestingManualResetEvent.Set()*/InvokeSpeechFailed());
        }

        public event EventHandler SpeechFailed;

        protected void InvokeSpeechFailed()
        {
            EventHandler handler = SpeechFailed;
            if (handler != null) handler(this, new EventArgs());
        }

        protected void NotifyPropertyChanged(string property)
        {
            Dispatcher.BeginInvoke(delegate
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(property));
                }
            });
        }

        private void OnPlayerBufferNeeded(object sender, EventArgs e)
        {
            if (_ignoreNextBufferNeeded)
            {
                _ignoreNextBufferNeeded = false;
            }
            else
            {
                IsSpeaking = false;
                _xnaUpdateTimer.Stop();
                _player.Stop();
                _ignoreNextBufferNeeded = true;
            }
        }

        public void Speak()
        {
            if (!IsSpeaking && (_lastPhraseBytes != null))
            {
                SpeakResult(_lastPhraseBytes);
            }
        }

        public void PauseSpeaking()
        {
            if (IsSpeaking)
            {
                _player.Pause();
            }
        }

        public void ResumeSpeaking()
        {
            if (IsSpeaking)
            {
                _player.Resume();
            }
        }

        private void SpeakResult(byte[] soundBytes)
        {
            byte[] buffer = ConvertTo16BitAudio(soundBytes, 0x80, soundBytes.Length - 0x80);
            if (buffer.Length > 0)
            {
                if (_xnaUpdateTimer == null)
                {
                    _xnaUpdateTimer = new DispatcherTimer();
                    _xnaUpdateTimer.Interval = new TimeSpan(0, 0, 0, 0, 256);
                    _xnaUpdateTimer.Tick += (s, e) => FrameworkDispatcher.Update();
                }
                if (_player == null)
                {
                    _player = new DynamicSoundEffectInstance(0x1f40, AudioChannels.Mono);
                    _player.BufferNeeded += OnPlayerBufferNeeded;
                }
                FrameworkDispatcher.Update();
                _ignoreNextBufferNeeded = true;
                _player.SubmitBuffer(buffer);
                _player.Play();
                FrameworkDispatcher.Update();
                _xnaUpdateTimer.Start();
                IsSpeaking = true;
            }
        }

        public bool IsSpeaking
        {
            get
            {
                return (bool)GetValue(IsSpeakingProperty);
            }
            set
            {
                SetValue(IsSpeakingProperty, value);
            }
        }

        public byte[] LastPhraseBytes
        {
            get
            {
                return _lastPhraseBytes;
            }
            set
            {
                _lastPhraseBytes = value;
                if (value != null)
                {
                    ResultValid = true;
                }
                else
                {
                    ResultValid = false;
                }
            }
        }

        public bool ResultValid
        {
            get
            {
                return _resultValid;
            }
            set
            {
                _resultValid = value;
                NotifyPropertyChanged("ResultValid");
            }
        }

        public static ManualResetEvent TestingManualResetEvent
        {
            get
            {
                if (_testingManualResetEvent == null)
                {
                    _testingManualResetEvent = new ManualResetEvent(false);
                }
                return _testingManualResetEvent;
            }
        }
    }

    public static class WebRequestHelper
    {
        // Methods
        private static RequestInfo CreateRequestInfo(string uriString, Action sent, Action<string> failed)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri(uriString));
            return new RequestInfo
            {
                Request = request,
                DataResponseType = DataResponseTypes.String,
                Sent = sent,
                Failed = failed
            };
        }

        private static void OnGetResponseCompleted(IAsyncResult ar)
        {
            RequestInfo asyncState = (RequestInfo)ar.AsyncState;
            try
            {
                Stream stream;
                HttpWebResponse response = (HttpWebResponse)asyncState.Request.EndGetResponse(ar);
                if (!asyncState.Cancelled)
                {
                    switch (asyncState.DataResponseType)
                    {
                        case DataResponseTypes.String:
                            using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                            {
                                asyncState.ReceivedString(reader.ReadToEnd());
                            }
                            break;

                        case DataResponseTypes.Byte:
                            stream = response.GetResponseStream();
                            byte[] buffer = new byte[stream.Length];
                            stream.Read(buffer, 0, (int)stream.Length);
                            asyncState.ReceivedBytes(buffer);
                            break;
                    }
                }
                return;
            }
            catch (Exception exception)
            {
                asyncState.Failed(exception.Message);
            }
        }

        public static RequestInfo SendByteRequest(string uriString, Action sent, Action<byte[]> received, Action<string> failed)
        {
            RequestInfo info = CreateRequestInfo(uriString, sent, failed);
            info.DataResponseType = DataResponseTypes.Byte;
            info.ReceivedBytes = received;
            info.BeginGetResponse();
            info.Sent();
            return info;
        }

        public static RequestInfo SendStringRequest(string uriString, Action sent, Action<string> received, Action<string> failed)
        {
            RequestInfo info = CreateRequestInfo(uriString, sent, failed);
            info.DataResponseType = DataResponseTypes.String;
            info.ReceivedString = received;
            info.BeginGetResponse();
            info.Sent();
            return info;
        }

        // Nested Types
        public enum DataResponseTypes
        {
            String,
            Byte
        }

        public class RequestInfo
        {
            // Fields
            public bool Cancelled;
            public DataResponseTypes DataResponseType;
            public Action<string> Failed;
            public Action<byte[]> ReceivedBytes;
            public Action<string> ReceivedString;
            public HttpWebRequest Request;
            public Action Sent;

            // Methods
            public void BeginGetResponse()
            {
                Request.BeginGetResponse(OnGetResponseCompleted, this);
            }
        }
    }
}
